package com.datastax.driver.mapping;

import com.datastax.driver.core.Session;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by dave on 18-7-26 下午12:04.
 */
public class MapperFactory extends MappingManager {
    private static Constructor<?> constructor;
    private static Field mappersMapField;
    private final ConcurrentHashMap<Object, Mapper<?>> mappers;

    @SuppressWarnings("unchecked")
    public MapperFactory(Session session) {
        super(session);
        try {
            mappersMapField = MappingManager.class.getDeclaredField("mappers");
            mappersMapField.setAccessible(true);
            mappers = (ConcurrentHashMap<Object, Mapper<?>>) mappersMapField.get(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        if (constructor == null) {
            try {
                Class<?> innerClass = Class.forName("com.datastax.driver.mapping.MappingManager$CacheKey");
                constructor = innerClass.getDeclaredConstructor(Class.class, String.class);
                constructor.setAccessible(true);
            } catch (ClassNotFoundException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

    }

    @Override
    public <T> CassandraMapper<T> mapper(Class<T> klass) {
        return getMapper(klass, null);
    }

    @Override
    public <T> CassandraMapper<T> mapper(Class<T> klass, String keyspace) {
        return getMapper(klass, keyspace);
    }

    @SuppressWarnings("unchecked")
    private <T> CassandraMapper<T> getMapper(Class<T> klass, String keyspace) {
        Object cacheKey;
        try {
            cacheKey = constructor.newInstance(klass, keyspace);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        CassandraMapper<T> mapper = (CassandraMapper<T>) mappers.get(cacheKey);
        if (mapper == null) {
            EntityMapper<T> entityMapper = AnnotationParser.parseEntity(klass, keyspace, this);
            mapper = new CassandraMapper<>(this, klass, entityMapper);
            CassandraMapper<T> old = (CassandraMapper<T>) mappers.putIfAbsent(cacheKey, mapper);
            if (old != null) {
                mapper = old;
            }
        }
        return mapper;
    }
}
